package ${package}.util;

import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.persistence.Entity;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.orm.jpa.persistenceunit.MutablePersistenceUnitInfo;
import org.springframework.orm.jpa.persistenceunit.PersistenceUnitPostProcessor;
import org.springframework.util.ClassUtils;
import org.springframework.util.SystemPropertyUtils;

/**
 * Searches supplied packages for @Entity-annotated classes and adds them to persistenceUnitInfo.
 */
public class JPAClassesPostProcessor implements PersistenceUnitPostProcessor, InitializingBean, ResourceLoaderAware {
    /**
     * List of packages to scan.
     */
    private List<String> packages;

    public void setPackages(final List<String> packages) {
        this.packages = packages;
    }

    /**
     * Persistent classes.
     */
    private Set<Class<?>> persistentClasses;

    /**
     * Entity annotation class.
     */
    private static final Class<Entity> ENTITY_ANNOTATION = Entity.class;

    /**
     * Resource pattern to search.
     */
    private static final String CLASS_RESOURCE_PATTERN = "**/*.class";

    /**
     * Resource pattern resolver.
     */
    private ResourcePatternResolver resourcePatternResolver;

    /**
     * Metadata reader factory.
     */
    private MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory();

    /**
     * Annotation type filter.
     */
    private TypeFilter annotationFilter = new AnnotationTypeFilter(ENTITY_ANNOTATION);

    /**
     * Looks for persistent classes in the classpath under the specified packages.
     *
     * @throws Exception exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        if (packages != null) {

            persistentClasses = new HashSet<Class<?>>();

            try {
                for (String p : packages) {
                    String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
                            + ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(p))
                            + "/"
                            + CLASS_RESOURCE_PATTERN;

                    Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);

                    for (Resource resource : resources) {
                        if (resource.isReadable()) {
                            MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);

                            if (this.annotationFilter.match(metadataReader, metadataReaderFactory)) {
                                persistentClasses.add(
                                        Class.forName(metadataReader.getAnnotationMetadata().getClassName()));
                            }
                        }
                    }
                }
            } catch (IOException ex) {
                throw new RuntimeException("I/O failure during classpath scanning", ex);
            }
        }
    }

    /**
     * Adds all the persistent classes found to the PersistentUnit.
     */
    @Override
    public void postProcessPersistenceUnitInfo(final MutablePersistenceUnitInfo persistenceUnitInfo) {
        for (Class<?> c : persistentClasses) {
            persistenceUnitInfo.addManagedClassName(c.getName());
        }
    }

    /**
     * Set the ResourceLoader that this object runs in.
     *
     * @param resourceLoader resource loader
     */
    @Override
    public void setResourceLoader(final ResourceLoader resourceLoader) {
        this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
        this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
    }
}
